package app

import (
	"archive/zip"
	"crypto/md5"
	"fmt"
	"io"
	"math/rand"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode"

	"github.com/jmoiron/sqlx"
)

// PathInfo 是一个结构体，用于封装路径的信息
type PathInfo struct {
	Path    string      // 路径
	Exists  bool        // 是否存在
	IsFile  bool        // 是否为文件
	IsDir   bool        // 是否为目录
	Size    int64       // 文件大小（字节）
	Mode    os.FileMode // 文件权限
	ModTime time.Time   // 文件修改时间
}

// FileWithModTime 表示文件及其最后修改时间
type FileWithModTime struct {
	Path    string
	ModTime time.Time
}

// 定义随机字符集
const charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

// 全局随机数生成器
var globalRand = rand.New(rand.NewSource(time.Now().UnixNano()))

// GenerateID 生成包含时间戳和指定长度随机字符的ID
// randomLength: 随机部分的长度
func GenerateID(randomLength int) string {
	if randomLength < 0 {
		return "" // 如果随机部分长度为负数，返回空字符串
	}

	// 获取当前时间的纳秒级时间戳
	timestamp := time.Now().UnixNano()

	// 使用strings.Builder进行字符串拼接
	var builder strings.Builder
	builder.Grow(8 + randomLength) // 预分配内存

	// 获取时间戳的后8位并转换为字符串
	timestampStr := strconv.FormatInt(timestamp%1e8, 10)

	// 将时间戳转换为字符串并拼接到builder中
	builder.WriteString(timestampStr)

	// 生成随机字符部分
	for i := 0; i < randomLength; i++ {
		builder.WriteByte(charset[globalRand.Intn(len(charset))])
	}

	// 返回最终拼接的字符串
	return builder.String()
}

// CheckPath 检查给定路径的信息
// path: 要检查的路径
func CheckPath(path string) (PathInfo, error) {
	// 创建一个 PathInfo 结构体
	var info PathInfo

	// 清理路径，确保没有多余的斜杠
	path = filepath.Clean(path)

	// 设置路径
	info.Path = path

	// 使用 os.Stat 获取文件状态
	fileInfo, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			// 如果路径不存在, 则直接返回
			info.Exists = false
			return info, fmt.Errorf("路径 '%s' 不存在，请检查路径是否正确: %s", path, os.ErrNotExist)
		} else {
			return info, fmt.Errorf("无法访问路径 '%s': %s", path, err)
		}
	}

	// 路径存在，填充信息
	info.Exists = true                // 标记路径存在
	info.IsFile = !fileInfo.IsDir()   // 通过取反判断是否为文件，因为 IsDir 返回 false 表示是文件
	info.IsDir = fileInfo.IsDir()     // 直接使用 IsDir 方法判断是否为目录
	info.Size = fileInfo.Size()       // 获取文件大小
	info.Mode = fileInfo.Mode()       // 获取文件权限
	info.ModTime = fileInfo.ModTime() // 获取文件的最后修改时间

	// 返回路径信息结构体
	return info, nil
}

// GetFileMD5Last8 获取文件的 MD5 哈希值的后 8 位
// 参数：
//
//	filePath - 文件路径
//
// 返回值：
//
//	string - 文件 MD5 哈希值的后 8 位
//	error - 如果发生错误，返回错误信息；否则返回 nil
func GetFileMD5Last8(filePath string) (string, error) {
	// 检查文件是否为空
	if filePath == "" {
		return "", fmt.Errorf("获取文件 MD5 哈希值失败: 文件路径为空")
	}

	// 检查文件是否存在
	if _, err := CheckPath(filePath); err != nil {
		return "", fmt.Errorf("文件不存在: %s", filePath)
	}

	// 打开文件
	file, err := os.Open(filePath)
	if err != nil {
		return "", fmt.Errorf("打开文件时出错: %w", err)
	}
	defer file.Close()

	// 创建 MD5 哈希对象
	hash := md5.New()

	// 分块读取文件内容
	buffer := make([]byte, 32*1024) // 32KB 缓冲区
	for {
		n, err := file.Read(buffer)
		if err != nil && err != io.EOF {
			return "", fmt.Errorf("读取文件内容时出错: %w", err)
		}
		if n == 0 {
			break // 文件读取完成
		}
		hash.Write(buffer[:n]) // 将读取的内容写入哈希对象
	}

	// 获取完整的 MD5 哈希值
	sum := hash.Sum(nil)

	// 将哈希值转换为十六进制字符串
	hashStr := fmt.Sprintf("%x", sum)

	// 返回哈希值的后 8 位
	return hashStr[len(hashStr)-8:], nil
}

// HumanReadableSize 获取文件大小并转换为人性化单位显示
// 参数：
//
//	filePath - 文件路径
//
// 返回值：
//
//	string - 文件大小的人性化表示
//	error - 如果发生错误，返回错误信息；否则返回 nil
func HumanReadableSize(filePath string) (string, error) {
	// 检查文件是否存在
	if _, err := CheckPath(filePath); err != nil {
		return "", fmt.Errorf("文件不存在: %s", filePath)
	}

	// 打开文件
	file, err := os.Stat(filePath)
	if err != nil {
		return "", fmt.Errorf("获取文件信息时出错: %w", err)
	}

	// 获取文件大小（以字节为单位）
	size := file.Size()

	// 定义单位和换算关系
	units := []string{"B", "KB", "MB", "GB"}
	base := float64(1024)

	// 转换为合适的单位
	var unit string
	sizeFloat := float64(size) // 将 size 转换为 float64
	if sizeFloat < base {
		unit = units[0]
	} else if sizeFloat < base*base {
		unit = units[1]
		sizeFloat /= base
	} else if sizeFloat < base*base*base {
		unit = units[2]
		sizeFloat /= base * base
	} else {
		unit = units[3]
		sizeFloat /= base * base * base
	}

	// 格式化输出
	return fmt.Sprintf("%.2f%s", sizeFloat, unit), nil
}

// GetZipFiles 获取指定目录下所有以 .zip 结尾的文件列表
// 参数：
//
//	dirPath - 目录路径
//
// 返回值：
//
//	[]string - 匹配的文件路径列表
//	error - 如果发生错误，返回错误信息；否则返回 nil
func GetZipFiles(dirPath, FileExtension string) ([]string, error) {
	// 检查目录是否存在
	if _, err := os.Stat(dirPath); os.IsNotExist(err) {
		return nil, fmt.Errorf("在获取文件列表时，目录不存在: %s", dirPath)
	}

	// 检查文件扩展名是否为空
	if FileExtension == "" {
		return nil, fmt.Errorf("在获取文件列表时，文件扩展名不能为空")
	}

	// 用于存储匹配的文件路径
	var zipFiles []string

	// 遍历目录及其子目录
	err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return fmt.Errorf("遍历文件时出错: %w", err)
		}

		// 检查文件是否以 指定扩展名 结尾
		if !info.IsDir() && strings.HasSuffix(info.Name(), FileExtension) {
			zipFiles = append(zipFiles, path)
		}

		return nil
	})

	if err != nil {
		return nil, fmt.Errorf("遍历目录时出错: %w", err)
	}

	return zipFiles, nil
}

// SortFilesByModTime 按照文件的最后修改时间对文件列表进行排序
func SortFilesByModTime(files []FileWithModTime) []FileWithModTime {
	// 检查文件列表是否为空
	if len(files) == 0 {
		return files
	}

	// 按照文件的最后修改时间排序
	sort.Slice(files, func(i, j int) bool {
		return files[i].ModTime.Before(files[j].ModTime)
	})
	return files
}

// RetainLatestFiles 处理文件列表，仅保留最新的指定数量的文件
// 参数：
//
//	db - 数据库连接
//	files - 文件路径列表
//	retainCount - 保留的文件数量
//
// 返回值：
//
//	error - 如果发生错误，返回错误信息；否则返回 nil
func RetainLatestFiles(db *sqlx.DB, files []string, retainCount int) error {
	// 捕获panic
	defer func() {
		if r := recover(); r != nil {
			appLog.Errorf("程序发生了panic: %v", r)
		}
	}()

	// 用于存储文件及其最后修改时间
	var fileInfos []FileWithModTime

	// 获取每个文件的最后修改时间
	for _, filePath := range files {
		info, err := os.Stat(filePath)
		if os.IsNotExist(err) {
			appLog.Errorf("文件不存在，跳过: %s", filePath)
			continue
		} else if err != nil {
			return fmt.Errorf("获取文件信息时出错: %w", err)
		}
		fileInfos = append(fileInfos, FileWithModTime{Path: filePath, ModTime: info.ModTime()})
	}

	// 检查列表是否为空
	if len(fileInfos) == 0 {
		return nil
	}

	// 按照文件的最后修改时间排序
	fileInfos = SortFilesByModTime(fileInfos)

	// 如果文件数量小于或等于保留数量，直接返回
	if len(fileInfos) <= retainCount {
		return nil
	}

	// 删除最早的文件，仅保留最新的指定数量的文件
	for i := 0; i < len(fileInfos)-retainCount; i++ {
		// 获取文件路径
		filePath := fileInfos[i].Path

		// 删除文件
		if err := os.Remove(filePath); err != nil {
			return fmt.Errorf("清理文件时出错: %w", err)
		}

		// 获取文件名称
		fileName := filepath.Base(filePath)

		// 构建清理记录的 SQL 语句
		deleteRecordSql := "DELETE FROM backup_record WHERE backup_file_name = ?"

		// 执行删除sql
		if _, err := db.Exec(deleteRecordSql, fileName); err != nil {
			return fmt.Errorf("清理备份记录时出错: %w", err)
		}

		appLog.Debugf("清理文件: %s", filePath)
	}

	return nil
}

// CreateZipFromOSPaths 根据目标目录和文件名创建ZIP压缩文件
// 参数:
//
//	db - 数据库连接(当前未使用，保留参数)
//	targetDir - 需要压缩的目标目录路径
//	targetName - 需要压缩的目标名称(文件或目录名)
//	backupFileNamePath - 备份文件的基础路径(不含扩展名)
//
// 返回值:
//
//	string - 生成的ZIP文件完整路径
//	error - 操作过程中遇到的错误
func CreateZipFromOSPaths(db *sqlx.DB, targetDir, targetName, backupFileNamePath string) (string, error) {
	// 检查目标目录是否为空
	if targetDir == "" {
		return "", fmt.Errorf("目标目录不能为空")
	}

	// 检查目标目录是否存在
	if _, err := CheckPath(targetDir); err != nil {
		return "", fmt.Errorf("目标目录不存在: %w", err)
	}

	// 检查目标名称是否为空
	if targetName == "" {
		return "", fmt.Errorf("目标名称不能为空")
	}

	// 检查备份文件的基础路径是否为空
	if backupFileNamePath == "" {
		return "", fmt.Errorf("备份文件的基础路径不能为空")
	}

	// 构建完整的压缩文件路径(添加扩展名)
	zipFilePath := fmt.Sprintf("%s%s", backupFileNamePath, ".zip")

	// 切换到目标目录以便后续操作
	if err := os.Chdir(targetDir); err != nil {
		return "", fmt.Errorf("切换到目标目录时出错: %w", err)
	}

	// 调用CreateZip函数执行实际压缩操作
	if err := CreateZip(zipFilePath, targetName); err != nil {
		return "", fmt.Errorf("压缩文件时出错: %w", err)
	}

	// 返回生成的ZIP文件完整路径
	return zipFilePath, nil
}

// UncompressFilesByOS 根据目标目录和文件名解压ZIP压缩文件
// 参数:
//
// zipDir - 需要解压的ZIP文件所在目录路径
//
//	zipFileName - 需要解压的ZIP文件名
//	outputPath - 解压后的文件存放路径
//
// 返回值:
//
//	string - 解压后的文件存放路径
//	error - 操作过程中遇到的错误
func UncompressFilesByOS(zipDir, zipFileName, outputPath string) (string, error) {
	// 检查解压输出路径是否存在
	if _, err := CheckPath(outputPath); err != nil {
		return "", fmt.Errorf("解压输出路径不存在: %w", err)
	}

	// 获取解压缩文件的完整路径, 例如: /home/backup/zip/20240506_123456.zip
	zipFilePath := filepath.Join(zipDir, zipFileName)

	// 检查解压缩文件是否存在
	if _, err := CheckPath(zipFilePath); err != nil {
		return "", fmt.Errorf("解压文件不存在: %w", err)
	}

	// 检查输出路径下是否存在同名
	baseName := strings.Split(zipFileName, "_")[0]  // 按下划线分割文件名，获取文件名称部分
	tempPath := filepath.Join(outputPath, baseName) // 构建临时路径
	if _, err := CheckPath(tempPath); err == nil {
		return "", fmt.Errorf("解压输出路径下存在同名: %s", tempPath)
	}

	// 调用解压函数
	if err := Unzip(zipFilePath, outputPath); err != nil {
		return "", fmt.Errorf("解压文件时出错: %w", err)
	}

	return outputPath, nil
}

// CreateZip 函数用于创建ZIP压缩文件
func CreateZip(zipFilePath string, sourceDir string) error {
	// 检查zipFilePath是否为绝对路径，如果不是，将其转换为绝对路径
	if !filepath.IsAbs(zipFilePath) {
		absPath, err := filepath.Abs(zipFilePath)
		if err != nil {
			return fmt.Errorf("转换zipFilePath为绝对路径失败: %w", err)
		}
		zipFilePath = absPath
	}
	// 检查sourceDir是否为绝对路径，如果不是，将其转换为绝对路径
	if !filepath.IsAbs(sourceDir) {
		absPath, err := filepath.Abs(sourceDir)
		if err != nil {
			return fmt.Errorf("转换sourceDir为绝对路径失败: %w", err)
		}
		sourceDir = absPath
	}

	// 创建 ZIP 文件
	zipFile, err := os.Create(zipFilePath)
	if err != nil {
		return fmt.Errorf("创建 ZIP 文件失败: %w", err)
	}
	defer zipFile.Close()

	// 创建 ZIP 写入器
	zipWriter := zip.NewWriter(zipFile)
	defer zipWriter.Close() // 确保在函数结束时关闭 ZIP 写入器

	// 遍历目录并添加文件到 ZIP 包
	err = filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return fmt.Errorf("遍历目录时出错: %w", err)
		}

		// 获取相对路径，保留顶层目录
		headerName, err := filepath.Rel(filepath.Dir(sourceDir), path)
		if err != nil {
			return fmt.Errorf("获取相对路径失败: %w", err)
		}

		// 替换路径分隔符为正斜杠（ZIP 文件格式要求）
		headerName = filepath.ToSlash(headerName)

		// 创建 ZIP 文件头
		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return fmt.Errorf("创建 ZIP 文件头失败: %w", err)
		}
		// 设置文件头的名称
		header.Name = headerName
		// 设置文件头的压缩方法
		header.Method = zip.Deflate

		// 如果是目录，直接写入文件头
		if info.IsDir() {
			header.Name += "/" // 确保目录名以斜杠结尾
			// 创建目录（如果需要）
			if _, err := zipWriter.CreateHeader(header); err != nil {
				return fmt.Errorf("创建 ZIP 目录失败: %w", err)
			}
			return nil
		}

		// 如果是文件，写入文件内容
		fileWriter, err := zipWriter.CreateHeader(header)
		if err != nil {
			return fmt.Errorf("创建 ZIP 写入器失败: %w", err)
		}

		// 打开文件并写入内容
		file, err := os.Open(path)
		if err != nil {
			return fmt.Errorf("打开文件失败: %w", err)
		}
		defer file.Close()

		// 使用 io.CopyBuffer 并指定缓冲区大小
		buffer := make([]byte, 1024*1024) // 1MB 缓冲区
		if _, err := io.CopyBuffer(fileWriter, file, buffer); err != nil {
			return fmt.Errorf("写入 ZIP 文件失败: %w", err)
		}

		return nil
	})

	if err != nil {
		return fmt.Errorf("打包目录到 ZIP 失败: %w", err)
	}

	return nil
}

// 解压缩 ZIP 文件到指定目录
func Unzip(zipFilePath string, targetDir string) error {
	// 打开 ZIP 文件
	zipReader, err := zip.OpenReader(zipFilePath)
	if err != nil {
		return fmt.Errorf("打开 ZIP 文件失败: %w", err)
	}
	defer zipReader.Close()

	// 确保目标目录存在
	if err := os.MkdirAll(targetDir, 0755); err != nil {
		return fmt.Errorf("创建目标目录失败: %w", err)
	}

	// 遍历 ZIP 文件中的每个文件或目录
	for _, file := range zipReader.File {
		// 获取目标路径
		targetPath := filepath.Join(targetDir, file.Name)

		// 创建目录（如果需要）
		if file.FileInfo().IsDir() {
			if err := os.MkdirAll(targetPath, file.Mode()); err != nil {
				return fmt.Errorf("创建目录失败: %w", err)
			}
			continue
		}

		// 创建文件
		fileWriter, err := os.OpenFile(targetPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
		if err != nil {
			return fmt.Errorf("创建文件失败: %w", err)
		}
		defer fileWriter.Close()

		// 打开 ZIP 文件中的文件
		zipFileReader, err := file.Open()
		if err != nil {
			return fmt.Errorf("打开 ZIP 文件中的文件失败: %w", err)
		}
		defer zipFileReader.Close()

		// 使用 io.CopyBuffer 并指定缓冲区大小
		buffer := make([]byte, 1024*1024) // 1MB 缓冲区
		if _, err := io.CopyBuffer(fileWriter, zipFileReader, buffer); err != nil {
			return fmt.Errorf("写入 ZIP 文件失败: %w", err)
		}
	}

	return nil
}

// ContainsSpecialChars 检测字符串是否包含特殊字符或危险字符
// 参数：s - 需要检测的字符串
// 返回：true 如果包含特殊字符或危险字符，false 否则
func ContainsSpecialChars(s string) bool {
	// 定义危险字符集合，使用哈希表存储以提高查找效率
	dangerousChars := map[rune]bool{
		'<': true, '>': true, '"': true, '\'': true, '\\': true, '`': true, ';': true,
		'%': true, '$': true, '#': true, '@': true, '&': true, '*': true, '(': true,
		')': true, '{': true, '}': true, '[': true, ']': true, '|': true, '!': true,
		'^': true, '~': true, '=': true, '+': true, '-': true, '.': true, ',': true,
		'/': true, '?': true, ':': true,
	}

	// 遍历字符串中的每个字符
	for _, char := range s {
		// 检查是否为危险字符
		if dangerousChars[char] {
			return true
		}
		// 检查是否为控制字符或非打印字符
		if unicode.IsControl(char) || !unicode.IsPrint(char) {
			return true
		}
	}

	// 如果没有找到任何特殊或危险字符，返回false
	return false
}

// SanitizePath 清理并获取路径的绝对路径
// 参数：path - 需要清理的路径指针
// 返回：error - 如果发生错误，返回错误信息；否则返回 nil
func SanitizePath(path *string) error {
	*path = filepath.Clean(*path)
	absPath, err := filepath.Abs(*path)
	if err != nil {
		return fmt.Errorf("获取路径绝对路径失败: %w", err)
	}
	*path = absPath
	return nil
}

// RenameBackupDirectory 重命名备份目录
// 参数：
//
//	rootPath - 备份存放目录的根路径
//	oldDirName - 旧的备份目录名称
//	newDirName - 新的备份目录名称
//
// 返回值：
//
//	error - 如果发生错误，返回错误信息；否则返回 nil
func RenameBackupDirectory(rootPath, oldDirName, newDirName string) error {
	if err := os.Chdir(rootPath); err != nil {
		return fmt.Errorf("切换到备份存放目录的rootPath失败: %w, Path: %s", err, rootPath)
	}

	if _, err := CheckPath(newDirName); err == nil {
		return fmt.Errorf("备份目录已存在: %s, 请重试", filepath.Join(rootPath, newDirName))
	}

	if _, err := CheckPath(oldDirName); err != nil {
		return fmt.Errorf("旧的备份目录不存在: %s", filepath.Join(rootPath, oldDirName))
	}

	if err := os.Rename(oldDirName, newDirName); err != nil {
		return fmt.Errorf("重命名备份目录失败: %w, Old: %s, New: %s", err, oldDirName, newDirName)
	}

	appLog.Successf("备份目录重命名成功: %s", filepath.Join(rootPath, newDirName))
	return nil
}
